//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "UI/Systems/Screen.h"
#include "UI/Widgets/Frame.h"
#include "UI/Systems/UserInterface.h"

#include "UI/Animation/Frame/DummyFrameAnimation.h"

namespace Engine
{
namespace UI
{
	
/*
=================================================
	constructor
=================================================
*/
	Screen::Screen (const EngineSubSystemsRef ess) :
		BaseObject(ess), _needComputeRegion(true), _timer(ess),
		_touchDelta(0.0f), _redraw(true), _lockInputFlags(0),
		_continuouslyRenderMode(true), _defaultAnim( DummyFrameAnimation::New( ess ) )
	{
		ESS()->GetEventSystem()->AttachEventListener(
			DelegateBuilder::Create( ScreenPtr(this), &Screen::_OnWindowEvent ), ESystemEvent::WINDOW );
	}
		
/*
=================================================
	destructor
=================================================
*/
	Screen::~Screen ()
	{
		ESS()->GetEventSystem()->DetachEventListener(
			DelegateBuilder::Create( ScreenPtr(this), &Screen::_OnWindowEvent ), ESystemEvent::WINDOW );
	}
	
/*
=================================================
	Refresh
=================================================
*/
	void Screen::Refresh ()
	{
		_needComputeRegion = true;
	}
	
/*
=================================================
	Redraw
=================================================
*/
	void Screen::Redraw ()
	{
		_redraw = true;
	}
	
/*
=================================================
	LockInput
=================================================
*/
	void Screen::LockInput (ELockInput::type flag)
	{
		_lockInputFlags |= (1 << flag);
	}
	
/*
=================================================
	UnlockInput
=================================================
*/
	void Screen::UnlockInput (ELockInput::type flag)
	{
		_lockInputFlags ^= (1 << flag);
	}

/*
=================================================
	ShowFrame
=================================================
*/
	bool Screen::ShowFrame (const FramePtr &frame)
	{
		return ShowFrame( frame, _defaultAnim );
	}
	
/*
=================================================
	ShowFrame
=================================================
*/
	bool Screen::ShowFrame (const FramePtr &frame, const IFrameAnimationPtr &anim)
	{
		CHECK_ERR( anim.IsNotNull() and frame.IsNotNull() );

		if ( _frameAnim.IsNotNull() ) {
			_frameAnim->Stop();
		}

		Redraw();

		_frameAnim = anim;
		_frameAnim->Start( frame, DelegateBuilder::Create( ScreenPtr(this), &Screen::_OnShowFrameAnimFinished ) );

		_frames.PushBack( frame );

		_needComputeRegion = true;
		return true;
	}

/*
=================================================
	CloseFrame
=================================================
*/
	bool Screen::CloseFrame (const FramePtr &frame)
	{
		CHECK_ERR( frame.IsNotNull() );
		CHECK_ERR( _frames.FindAndErase( frame ) );
		
		frame->OnClose();
		
		_needComputeRegion = true;
		return true;
	}

/*
=================================================
	CloseCurrentFrame
=================================================
*/
	bool Screen::CloseCurrentFrame ()
	{
		return CloseCurrentFrame( _defaultAnim );
	}
	
/*
=================================================
	CloseCurrentFrame
=================================================
*/
	bool Screen::CloseCurrentFrame (const IFrameAnimationPtr &anim)
	{
		CHECK_ERR( anim.IsNotNull() );
		
		if ( _frameAnim.IsNotNull() ) {
			_frameAnim->Stop();
		}
		
		if ( _frames.Empty() ) {
			_frameAnim = null;
			return false;
		}
		
		Redraw();

		_frameAnim = anim;
		_frameAnim->Start( _frames.Back(), DelegateBuilder::Create( ScreenPtr(this), &Screen::_OnCloseFrameAnimFinished ) );

		_needComputeRegion = true;
		return true;
	}

/*
=================================================
	CloseAll
=================================================
*/
	bool Screen::CloseAll ()
	{
		frame_stack_t	tmp;
		tmp.SwapMem( _frames );

		FOR( i, tmp )
		{
			FramePtr	frame = tmp.Back();

			tmp.PopBack();

			frame->OnClose();
		}

		if ( _frames.Empty() )
		{
			_frames.SwapMem( tmp );
			_frames.ClearMem();
		}
		
		_needComputeRegion = true;
		return true;
	}

/*
=================================================
	IsVisible
=================================================
*/
	bool Screen::IsVisible (const FramePtr &frame) const
	{
		return not _frames.Empty() and frame == _frames.Back();
	}
	
/*
=================================================
	GetStackDepthFor
=================================================
*/
	uint Screen::GetStackDepthFor (const FramePtr &frame) const
	{
		uint	depth = 0;

		FOR_rev( i, _frames )
		{
			if ( _frames[i] == frame ) {
				return depth;
			}
			++depth;
		}
		return uint(-1);
	}

/*
=================================================
	Draw
=================================================
*/
	bool Screen::Draw (float timeDelta)
	{
		typedef WidgetMsg	WM;

		_timer.Update( timeDelta );

		if ( _frames.Empty() )
			return false;


		// Arrange
		if ( _needComputeRegion )
		{
			Redraw();
			_needComputeRegion = false;

			FOR_rev( i, _frames )
			{
				Ptr< Frame >	frame = _frames[i].ptr();

				frame->ReArrange();
				
				if ( frame->IsOverlay() )
					break;
			}
		}
		

		// Update
		_data.Reset();

		if ( not IsInputLocked() ) {
			_UpdateInput();
		}

		FOR_rev( i, _frames )
		{
			Ptr< Frame >	frame = _frames[i].ptr();

			frame->ProcessMessage( WM( WM::UPDATE, WM::Update( this, timeDelta, _data ) ) );
			
			if ( frame->IsExclusiveInput() or frame->IsOverlay() )
				break;
		}


		// Draw
		const bool	anim_enabled = _frameAnim.IsNotNull();

		if ( not _continuouslyRenderMode and _data.widgetsChanged == 0 and not _redraw and not anim_enabled )
			return false;

		_redraw = false;

		ESS()->GetGraphicsEngine()->GetViewport()->Mode2D();

		Graphics::BatchRenderPtr const &	br = ESS()->GetGraphicsEngine()->GetBatchRender();

		CHECK_ERR( br.IsNotNull() );


		usize	first = _frames.LastIndex();

		FOR_rev( i, _frames )
		{
			if ( _frames[i]->IsOverlay() )
			{
				first = i;
				break;
			}
		}


		if ( anim_enabled )
		{
			_frameAnim->Draw( first, _frames, timeDelta );
		}
		else
		{
			for (; first < _frames.Count(); ++first)
			{
				Ptr< Frame >	frame = _frames[ first ].ptr();

				frame->ProcessMessage( WM( WM::DRAW, WM::Draw( this ) ) );
			}
		}
		
		return true;
	}
	
/*
=================================================
	_UpdateInput
=================================================
*/
	void Screen::_UpdateInput ()
	{
		using namespace Graphics;

		enum {
			TOUCH_ID	= 0,	// use only 0 touch ID for UI
		};

		const float	c_min_touch_delta	= 5.0f;	// mm
		const float	c_touch_size		= 5.0f;	// mm


		Ptr< Input >			input		= ESS()->GetInput();
		ViewportPtr const &		vp			= ESS()->GetGraphicsEngine()->GetViewport();
		Input::Touch const &	touch		= input->GetTouch( TOUCH_ID );
		float					touch_size	= ESS()->GetApplication()->GetPlatform()->GetDisplay().
												ToUNorm( Display::PhysicsCoord( c_touch_size, c_touch_size ) ).Get().Min();

		// click
		if ( touch.IsPressed() )
		{
			_data.touchType	= UpdateWidgetData::PRESSED;
			_data.touchPos	= vp->PositionToNormalized( touch.pos );
			_data.touchSize	= vec2( touch_size );

			_touchDelta +=	ESS()->GetApplication()->GetPlatform()->GetDisplay().ToPhysics(
								ScreenCoords::Pixels( touch.delta )
							).Get().Length();
		}
		else
		{
			if ( touch.IsClicked() and _touchDelta <= c_min_touch_delta )
			{
				_data.touchType	= UpdateWidgetData::CLICK;
				_data.touchPos	= vp->PositionToNormalized( touch.pos );
				_data.touchSize	= vec2( touch_size );
			}
			_touchDelta = 0.0f;
		}


		// motion
		/*if ( IsNotZero( input->GetTouch( TOUCH_ID ).delta ) )
		{
			if ( delta_len > min_touch_delta )
			{
				// TODO: send motion
			}
			else
			{
				// accumulate small delta
				_accumDelta += input->GetTouch( TOUCH_ID ).delta;
			}
		}*/
	}

/*
=================================================
	_OnWindowEvent
=================================================
*/
	void Screen::_OnWindowEvent (const SysEvent &ev)
	{
		typedef WidgetMsg	WM;

		if ( ev.window.type == SysEvent::Window::RESIZED )
		{
			_needComputeRegion = true;
		}
	}
	
/*
=================================================
	OnAttached
=================================================
*/
	void Screen::OnAttached ()
	{
		ESS()->GetUserInterface()->GetActionManager()->AddAction( "CloseFrame",
			UIAction( DelegateBuilder::Create( ScreenPtr(this), &Screen::_Action_CloseFrame ) ) );
	}
	
/*
=================================================
	OnDetached
=================================================
*/
	void Screen::OnDetached ()
	{
		ESS()->GetUserInterface()->GetActionManager()->RemoveAction( "CloseFrame" );
	}
	
/*
=================================================
	_Action_CloseFrame
=================================================
*/
	void Screen::_Action_CloseFrame (Widget *wgt)
	{
		WidgetPtr	tmp = wgt;

		while ( tmp.IsNotNull() )
		{
			wgt = tmp.ptr();
			tmp = wgt->GetParent();
		}

		if ( wgt->GetType() == EWidget::FRAME )
		{
			CloseFrame( (Frame *) wgt );
		}
		else
		{
			WARNING( "can't find Frame" );
		}
	}
	
/*
=================================================
	_OnShowFrameAnimFinished
=================================================
*/
	void Screen::_OnShowFrameAnimFinished ()
	{
		_frameAnim = null;
		Redraw();
	}
	
/*
=================================================
	_OnCloseFrameAnimFinished
=================================================
*/
	void Screen::_OnCloseFrameAnimFinished ()
	{
		_frameAnim = null;
		Redraw();
		
		FramePtr	frame = _frames.Back();

		_frames.PopBack();
		frame->OnClose();
	}


}	// UI
}	// Engine